﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Xml;
using System.Reflection;

namespace DynamicToolkit
{
    /// <summary>
    /// XPath step generator
    /// </summary>
    static class Step
    {
        public static string Root(XmlDocument doc)
        {
            if (doc == null)
            {
                throw new ArgumentNullException("doc");
            }

            return string.Format("/{0}", doc.DocumentElement.Name);
        }

        public static string Root(XmlElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            Stack<XmlNode> ancestors = new Stack<XmlNode>();

            XmlNode cur = element;

            while ((cur != null) && !(cur is XmlDocument))
            {
                ancestors.Push(cur);
                cur = cur.ParentNode;
            }

            StringBuilder buf = new StringBuilder();

            foreach (XmlNode node in ancestors)
            {
                buf.Append(Step.Child(node.Name));
            }

            return buf.ToString();
        }

        public static string Child(string elementName)
        {
            if (string.IsNullOrWhiteSpace(elementName))
            {
                throw new ArgumentException("Element name can't be null or empty");
            }

            return string.Format("/{0}", elementName);
        }

        public static string Attribute(string attributeName)
        {
            if (string.IsNullOrWhiteSpace(attributeName))
            {
                throw new ArgumentException("Attribute name can't be null or empty");
            }

            return string.Format("/@{0}", attributeName);
        }

        public static string Predicate(string predicate)
        {
            if (string.IsNullOrWhiteSpace(predicate))
            {
                throw new ArgumentException("Predicate name can't be null or empty");
            }

            return string.Format("[{0}]", predicate);
        }

        public static string Predicate(params Example[] examples)
        {
            if (examples == null || examples.Length == 0)
            {
                throw new ArgumentException("Predicate examples can't be null or empty");
            }

            StringBuilder buf = new StringBuilder("[");
            bool wrapExpressionsInParenthesis = examples.Length > 1;

            for (int i = 0; i < examples.Length; i++)
            {
                Example example = examples[i];

                if (i > 0)
                {
                    buf.Append(" or ");
                }

                buf.Append(ConvertExampleToExpression(example, wrapExpressionsInParenthesis));
            }

            buf.Append("]");
            return buf.ToString();
        }

        private static string ConvertExampleToExpression(Example example, bool wrap)
        {
            StringBuilder buf = new StringBuilder();

            if (wrap)
            {
                buf.Append("(");
            }

            BindingFlags bindings = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.DeclaredOnly | BindingFlags.Instance;
            PropertyInfo[] properties = example.ExampleObject.GetType().GetProperties(bindings);

            if (properties.Length == 0)
            {
                throw new ArgumentException("Example does not contain any properties.  Can't convert to expression.");
            }

            for (int i = 0; i < properties.Length; i++)
            {
                PropertyInfo property = properties[i];
                object value = property.GetValue(example.ExampleObject, null);

                if (i > 0)
                {
                    buf.Append(" and ");
                }

                if (example.IsAttribute)
                {
                    buf.Append("@");
                }

                buf.AppendFormat("{0} = {1}", property.Name, XpathHelper.EscapeXpathValue(value));
            }

            if (wrap)
            {
                buf.Append(")");
            }

            return buf.ToString();
        }

        public static string Index(int index)
        {
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("index", "path index must at least zero");
            }

            return string.Format("[{0}]", (index + 1));
        }

        public static string Predicate(LambdaExpression exp)
        {
            if (exp == null)
            {
                throw new ArgumentNullException("exp", "Expression can not be null");
            }

            string predicate = XpathExpressionParser.Expression(exp);
            return Step.Predicate(predicate);
        }
    }
}
